package com.gitee.qdbp.base.controlling;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gitee.qdbp.able.beans.ParamBuffer;
import com.gitee.qdbp.able.exception.ServiceException;
import com.gitee.qdbp.able.model.reusable.RemoteAttachment;
import com.gitee.qdbp.able.result.IBatchResult;
import com.gitee.qdbp.able.result.IResultMessage;
import com.gitee.qdbp.able.result.ResponseMessage;
import com.gitee.qdbp.able.result.ResultCode;
import com.gitee.qdbp.base.annotation.OperateRecord;
import com.gitee.qdbp.base.enums.OperateType;
import com.gitee.qdbp.base.system.model.IAccount;
import com.gitee.qdbp.base.system.model.IAgencyAccount;
import com.gitee.qdbp.base.utils.SessionTools;
import com.gitee.qdbp.base.utils.WebTools;
import com.gitee.qdbp.general.common.api.files.model.FolderName;
import com.gitee.qdbp.general.common.api.files.service.IWebFileService;
import com.gitee.qdbp.general.common.api.oplog.model.OperateRecordBean;
import com.gitee.qdbp.general.common.api.oplog.model.OperateRecordOptions;
import com.gitee.qdbp.general.common.api.oplog.service.IOperateRecordExecutor;
import com.gitee.qdbp.general.common.api.oplog.service.IOperateTracesExecutor;
import com.gitee.qdbp.general.common.api.sequence.service.ILocalSequenceGenerator;
import com.gitee.qdbp.general.common.enums.LogRequestType;
import com.gitee.qdbp.general.common.enums.LogResultType;
import com.gitee.qdbp.tools.utils.ConvertTools;
import com.gitee.qdbp.tools.utils.DateTools;
import com.gitee.qdbp.tools.utils.JsonTools;
import com.gitee.qdbp.tools.utils.StringTools;
import com.gitee.qdbp.tools.utils.VerifyTools;

/**
 * 操作日志处理类
 *
 * @author zhaohuihua
 * @version 160426
 */
public abstract class OperateRecordBaseControlling implements IOperateRecordControlling {

    private static final Logger log = LoggerFactory.getLogger(OperateRecordBaseControlling.class);

    private IControllingVars vars;

    private ExecutorService executors = Executors.newCachedThreadPool();

    @Autowired(required = false)
    private IWebFileService webFileService;
    @Autowired
    private ILocalSequenceGenerator sequenceGenerator;

    /** 对象字符串最大长度(0为不限制) **/
    private int objectStringMaxLength = 1000;
    /** 字段值字符串最大长度(0为不限制) **/
    private int valueStringMaxLength = 100;

    /**
     * 日志级别枚举<br>
     * 对应日志级别: ALL=trace, RESPONSE_BODY=debug, EXECUTE=info, warn, error<br>
     * ALL=RESPONSE_BODY+VIEWS+其他, RESPONSE_BODY=EXECUTE+QUERY<br>
     */
    public static enum LogLevel {
        ALL, RESPONSE_BODY, EXECUTE, WARN, ERROR, NONE
    }

    /** 保存上传的文件副本的方式枚举类 **/
    public static enum CopyUploadFileMode {
        ALL, SUCCESS, ERROR, NONE
    }

    /** 日志包路径的前缀(用于logback.xml文件将系统日志输出到单独的文件中) **/
    private String logPackagePrefix = "syslog.";

    @Autowired(required = false)
    private IOperateRecordExecutor operateRecordExecutor;
    @Autowired(required = false)
    private IOperateTracesExecutor operateTracesExecutor;

    /** 要将哪些级别的操作日志保存到数据库 **/
    private LogLevel saveToDb = LogLevel.NONE;

    // 为避免系统日志刷屏, 同一类错误1小时只输出一次
    /** 最近检查Service为空的时间 **/
    private long lastTimeOfServiceIsNull = 0;
    /** 最近一次保存至数据库发生异常的时间 **/
    private long lastTimeOfSaveToDbError = 0;

    /** 接口执行超过多久计为WARN(0为不限制) **/
    private int executeTimeout = 0;
    /** 接口执行调多少次数据库计为WARN(0为不限制) **/
    private int dbInvokeMaxTimes = 8;

    /** 日志所属工程模块 **/
    private String project;

    /** 保存上传的文件副本的方式 **/
    private CopyUploadFileMode copyUploadFileMode;
    /** 保存上传的文件副本的文件名后缀, 不指定表示不限制即全部保存 **/
    private List<String> copyUploadFileExtension;

    /** 哪些参数不用记录日志 **/
    private Map<String, Boolean> ignoreParamKeys = new HashMap<>();

    public OperateRecordBaseControlling(IControllingVars vars) {
        this.vars = vars;
        this.ignoreParamKeys.put("_", true);
    }

    /**
     * 判断Controller方法上有没有配置@ResponseBody
     * 
     * @return 有没有@ResponseBody
     */
    private boolean hasResponseBodyAnnotation(Method method) {
        if (method == null) return false;
        ResponseBody annotation = method.getAnnotation(ResponseBody.class);
        return annotation != null;
    }

    /** 判断是不是查询权限 **/
    protected boolean isQueryPermission(String key) {
        return key.endsWith(":query");
    }

    @Override
    public void start() {
        vars.start();
    }

    protected void clear() {
        vars.clear();
    }

    /** 提供给外部调用的方法 **/
    @Override
    public void save(Class<?> clazz, String method, String operateDesc, Object returns, IAccount operator) {
        String sign = clazz.getSimpleName() + "." + method + "()";
        try {
            save(clazz, null, sign, operateDesc, null, null, returns, LogLevel.EXECUTE, null, operator);
        } finally {
            vars.clear();
        }
    }

    /** 获取当前操作的用户, operator=入参传入的用户 **/
    protected IAccount getOperateUser(IAccount account) {
        IAccount user = account;
        if (user == null) user = vars.getAccount();
        if (user == null) user = SessionTools.checkLoginUser();
        return user;
    }

    /** 获取当前操作的所属分组, 同一用户分为一组 **/
    protected String getOperateGroups() {
        Session session = SecurityUtils.getSubject().getSession();
        String groups = (String) session.getAttribute("OperateRecordGroup");
        if (groups == null) {
            try {
                groups = sequenceGenerator.generate("OperateRecordGroup");
            } catch (ServiceException e) {
                groups = session.getId().toString();
            }
            session.setAttribute("OperateRecordGroup", groups);
        }
        return groups;
    }

    /** 获取租户编号, 如果user信息为null即表示用户未登录 **/
    protected String getTenantCode(IAccount user) {
        return user == null ? null : user.getTenantCode();
    }

    /** 补充业务数据 **/
    protected void fillBizField(OperateRecordBean recored, IAccount user) {
    }

    protected void save(Class<?> clazz, Method method, String sign, String operateDesc, String permission,
            OperateRecord annotation, Object returns, LogLevel level, MultipartFile file, IAccount operator) {
        // 获取Request参数
        HttpServletRequest req = WebTools.me.getRequest();
        IAccount user = getOperateUser(operator);

        Date startTime = vars.getStartTime(true);
        if (startTime == null) {
            throw new IllegalStateException("If want to call save(), you must call start() first.");
        }

        boolean isResponseBody = hasResponseBodyAnnotation(method);

        List<OperateTracesBean> items = vars.getTraces(true);
        Integer dbInvokeTimes = vars.getDbInvokeTimes(true);
        long executeTime = System.currentTimeMillis() - startTime.getTime();
        // 构造入库参数
        OperateRecordBean recored = new OperateRecordBean();
        recored.setProject(project);
        recored.setGroups(getOperateGroups());
        recored.setCreateTime(startTime);
        recored.setTenantCode(getTenantCode(user));
        // 用户信息
        if (user != null) {
            recored.setUserId(user.getId());
            recored.setUserName(user.getDisplayName());
        }
        if (user instanceof IAgencyAccount) { // 代客操作
            IAccount agent = ((IAgencyAccount) user).getAgent();
            recored.setAgencyOperate(true);
            recored.setAgentTenantCode(agent.getTenantCode());
            recored.setAgentId(agent.getId());
            recored.setAgentName(agent.getDisplayName());
        } else {
            recored.setAgencyOperate(false);
        }

        // 请求信息
        recored.setRequestType(isResponseBody ? LogRequestType.ACTION : LogRequestType.PAGE);
        if (req != null) {
            recored.setIp(WebTools.me.getIpAddress());
            recored.setRequestProtocol(req.getProtocol());
            recored.setRequestMethod(req.getMethod());
            recored.setRequestUrl(req.getRequestURL().toString());

            // 请求参数
            Map<String, Object> map = new HashMap<>();
            for (Enumeration<String> e = req.getParameterNames(); e.hasMoreElements();) {
                String k = e.nextElement();
                if (!this.ignoreParamKeys.containsKey(k)) {
                    // 避免字符串过长
                    String v = StringTools.ellipsis(req.getParameter(k), valueStringMaxLength);
                    map.put(k, v);
                }
            }
            String params = map.isEmpty() ? null : JsonTools.toJsonString(map);
            recored.setRequestParams(params);

            recored.setRefererUrl(req.getHeader("referer"));
            recored.setUserAgent(req.getHeader("user-agent"));
        }
        // 执行信息
        recored.setExecuteDesc(operateDesc);
        recored.setExecuteMethod(sign);
        recored.setExecuteTime((int) executeTime);
        recored.setDbInvokeTimes(dbInvokeTimes);

        fillBizField(recored, user);

        Runner runner = new Runner();
        runner.setLevel(level);
        runner.setPackages(logPackagePrefix + clazz.getName());
        runner.setResponseBody(isResponseBody);
        runner.setAnnotation(annotation);
        runner.setPermission(permission);
        runner.setReturns(returns);
        runner.setOperateRecord(recored);
        runner.setOperateTracess(items);
        runner.setUploadFile(file);

        executors.execute(runner);
    }

    private LogLevel switchFileLevel(Logger log) {
        // ALL=trace, RESPONSE_BODY=debug, EXECUTE=info, warn, error
        if (log.isTraceEnabled()) {
            return LogLevel.ALL;
        } else if (log.isDebugEnabled()) {
            return LogLevel.RESPONSE_BODY;
        } else if (log.isInfoEnabled()) {
            return LogLevel.EXECUTE;
        } else if (log.isWarnEnabled()) {
            return LogLevel.WARN;
        } else if (log.isErrorEnabled()) {
            return LogLevel.ERROR;
        } else {
            return LogLevel.NONE;
        }
    }

    protected void saveToFile(Logger log, LogLevel level, String message) {
        switch (level) {
        case ALL:
            log.trace(message);
            break;
        case RESPONSE_BODY:
            log.debug(message);
            break;
        case EXECUTE:
            log.info(message);
            break;
        case WARN:
            log.warn(message);
            break;
        case ERROR:
            log.error(message);
            break;
        case NONE:
            break;
        }
    }

    /** 操作记录保存到数据库 **/
    protected void saveToDb(OperateRecordBean bean) {
        try {
            operateRecordExecutor.create(bean);
        } catch (ServiceException e) {
            long mills = System.currentTimeMillis();
            if (mills - lastTimeOfSaveToDbError > DateTools.RATE_HOUR) {
                lastTimeOfSaveToDbError = mills;
                log.warn("Save operate recored to db error. {}{}", e.toString(), JsonTools.newlineLogs(bean), e);
            }
        }
    }

    /** 操作记录明细保存到数据库 **/
    protected void saveToDb(List<OperateTracesBean> list) {
        if (list.isEmpty()) {
            return;
        }
        try {
            operateTracesExecutor.create(list);
        } catch (ServiceException e) {
            long mills = System.currentTimeMillis();
            if (mills - lastTimeOfSaveToDbError > DateTools.RATE_HOUR) {
                lastTimeOfSaveToDbError = mills;
                log.warn("Save operate traces to db error. {}{}", e.toString(), JsonTools.newlineLogs(list), e);
            }
        }
    }

    /** 保存附件到文件服务器 **/
    protected String saveUploadFile(MultipartFile file) {
        try {
            String name = file.getOriginalFilename();
            InputStream input = file.getInputStream();
            return webFileService.upload(FolderName.temps, name, input, file.getSize());
        } catch (Throwable e) {
            log.debug("Save temporary file to FileService error, {}", e.toString());
            return null;
        }
    }

    /** 判断是否需要保存附件 **/
    protected boolean isNeedCopyUploadFile(MultipartFile file, LogResultType resultType) {
        if (file != null && webFileService != null && copyUploadFileMode != null) {
            CopyUploadFileMode mode;
            if (resultType == LogResultType.SUCCESS) {
                mode = CopyUploadFileMode.SUCCESS;
            } else {
                mode = CopyUploadFileMode.ERROR;
            }
            if (mode.ordinal() >= this.copyUploadFileMode.ordinal()) {
                return true;
            }
        }
        return false;
    }

    /** 要将哪些级别的日志保存到数据库 **/
    public LogLevel getSaveToDb() {
        return saveToDb;
    }

    /** 要将哪些级别的日志保存到数据库 **/
    public void setSaveToDb(LogLevel saveToDb) {
        this.saveToDb = saveToDb;
    }

    /** 接口执行超过多久计为WARN(0为不限制) **/
    public int getExecuteTimeout() {
        return executeTimeout;
    }

    /** 接口执行超过多久计为WARN(0为不限制) **/
    public void setExecuteTimeout(int executeTimeout) {
        this.executeTimeout = executeTimeout;
    }

    /** 接口执行调多少次数据库计为WARN(0为不限制) **/
    public int getDbInvokeMaxTimes() {
        return dbInvokeMaxTimes;
    }

    /** 接口执行调多少次数据库计为WARN(0为不限制) **/
    public void setDbInvokeMaxTimes(int dbInvokeMaxTimes) {
        this.dbInvokeMaxTimes = dbInvokeMaxTimes;
    }

    /** 接口执行调多少次数据库计为WARN(0为不限制) **/
    public void setCallDaoMaxTimes(int dbInvokeMaxTimes) {
        this.dbInvokeMaxTimes = dbInvokeMaxTimes;
    }

    /** 对象字符串最大长度(0为不限制) **/
    public int getObjectStringMaxLength() {
        return objectStringMaxLength;
    }

    /** 对象字符串最大长度(0为不限制) **/
    public void setObjectStringMaxLength(int objectStringMaxLength) {
        this.objectStringMaxLength = objectStringMaxLength;
    }

    /** 字段值字符串最大长度(0为不限制) **/
    public int getValueStringMaxLength() {
        return valueStringMaxLength;
    }

    /** 字段值字符串最大长度(0为不限制) **/
    public void setValueStringMaxLength(int valueStringMaxLength) {
        this.valueStringMaxLength = valueStringMaxLength;
    }

    /** 哪些参数不用记录日志 **/
    public void setIgnoreParamKeys(List<String> ignoreParamKeys) {
        this.ignoreParamKeys.clear();
        for (String string : ignoreParamKeys) {
            this.ignoreParamKeys.put(string, true);
        }
    }

    /** 日志所属工程模块 **/
    public String getProject() {
        return project;
    }

    /** 日志所属工程模块 **/
    public void setProject(String project) {
        this.project = project;
    }

    /** 日志包路径的前缀(用于logback.xml文件将系统日志输出到单独的文件中) **/
    public String getLogPackagePrefix() {
        return logPackagePrefix;
    }

    /** 日志包路径的前缀(用于logback.xml文件将系统日志输出到单独的文件中) **/
    public void setLogPackagePrefix(String logPackagePrefix) {
        this.logPackagePrefix = logPackagePrefix;
    }

    /** 保存上传的文件副本的方式 **/
    public CopyUploadFileMode getCopyUploadFileMode() {
        return copyUploadFileMode;
    }

    /** 保存上传的文件副本的方式 **/
    public void setCopyUploadFileMode(CopyUploadFileMode copyUploadFileMode) {
        this.copyUploadFileMode = copyUploadFileMode;
    }

    /** 保存上传文件副本的文件名后缀, 如.xls|.xlsx|.doc|.docx **/
    public List<String> getCopyUploadFileExtension() {
        return copyUploadFileExtension;
    }

    /** 保存上传文件副本的文件名后缀, 如.xls|.xlsx|.doc|.docx **/
    public void setCopyUploadFileExtension(List<String> copyUploadFileExtension) {
        if (copyUploadFileExtension != null) {
            this.copyUploadFileExtension = new ArrayList<>();
            for (String string : copyUploadFileExtension) {
                if (VerifyTools.isBlank(string)) continue;
                if (string.startsWith(".")) {
                    this.copyUploadFileExtension.add(string);
                } else {
                    this.copyUploadFileExtension.add("." + string);
                }
            }
        }
    }

    /** 保存上传文件副本的文件名后缀, 如.xls|.xlsx|.doc|.docx **/
    public void setCopyUploadFileExtensionByString(String copyUploadFileExtension) {
        String[] array = StringTools.split(copyUploadFileExtension);
        this.setCopyUploadFileExtension(ConvertTools.toList(array));
    }

    private LogResultType findResultType(Object returns) {
        if (returns instanceof Throwable) {
            return LogResultType.FAILED;
        } else if (returns instanceof IResultMessage) {
            IResultMessage result = (IResultMessage) returns;
            String code = result.getCode();
            if (!ResultCode.SUCCESS.getCode().equals(code)) {
                return LogResultType.FAILED;
            } else {
                if (returns instanceof ResponseMessage) {
                    ResponseMessage resp = (ResponseMessage) returns;
                    if (resp.getBody() instanceof IBatchResult) {
                        if (VerifyTools.isNotBlank(((IBatchResult) resp.getBody()).getFailed())) {
                            // 批量操作只要有失败的就算失败
                            return LogResultType.FAILED;
                        }
                    }
                }
                return LogResultType.SUCCESS;
            }
        } else {
            return LogResultType.UNKNOWN;
        }
    }

    private class Runner implements Runnable {

        private LogLevel level;
        private String packages;
        private boolean isResponseBody;
        private OperateRecord annotation;
        private String permission;
        private Object returns;
        private OperateRecordBean operateRecord;
        private List<OperateTracesBean> operateItems;
        private MultipartFile file;

        @Override
        public void run() {

            Logger log = LoggerFactory.getLogger(packages);

            if (operateItems == null) {
                operateItems = new ArrayList<>();
            }

            // 获取返回码
            String code = null;
            if (returns instanceof IResultMessage) {
                IResultMessage result = (IResultMessage) returns;
                code = result.getCode();
            }

            Date startTime = this.operateRecord.getCreateTime();
            Integer executeTime = this.operateRecord.getExecuteTime();
            Integer dbInvokeTimes = this.operateRecord.getDbInvokeTimes();

            LogLevel level;
            if (this.level != null) {
                level = this.level;
            } else {
                // 计算日志级别
                // 如果返回的是IResultMessage, 就是ResponseBody, 再判断返回码, 如果不成功就是ERROR
                level = LogLevel.ALL;
                if (executeTimeout > 0 && executeTime >= executeTimeout) {
                    level = LogLevel.WARN;
                } else if (dbInvokeMaxTimes > 0 && (dbInvokeTimes != null && dbInvokeTimes >= dbInvokeMaxTimes)) {
                    level = LogLevel.WARN;
                } else if (returns instanceof Throwable) {
                    level = LogLevel.ERROR;
                } else if (returns instanceof IResultMessage) {
                    if (ResultCode.SUCCESS.getCode().equals(code)) {
                        if (permission == null || isQueryPermission(permission)) {
                            level = LogLevel.RESPONSE_BODY;
                        } else {
                            level = LogLevel.EXECUTE;
                        }
                    } else {
                        level = LogLevel.ERROR;
                    }
                } else if (annotation != null && annotation.type() != OperateType.OTHER) { // OTHER:自动识别
                    if (annotation.type() == OperateType.QUERY) {
                        level = LogLevel.RESPONSE_BODY;
                    } else {
                        level = LogLevel.EXECUTE;
                    }
                } else if (isResponseBody) {
                    if (permission != null && isQueryPermission(permission)) {
                        level = LogLevel.RESPONSE_BODY;
                    } else {
                        level = LogLevel.EXECUTE;
                    }
                }
            }

            LogLevel saveToFile = switchFileLevel(log);

            if (file == null && level.ordinal() < saveToFile.ordinal() && level.ordinal() < saveToDb.ordinal()) {
                return;
            }

            String curr = DateTools.toNormativeString(startTime);

            String text = null;
            if (returns != null) {
                if (returns instanceof CharSequence) {
                    text = returns.toString();
                } else if (returns instanceof Throwable) {
                    String m = ((Throwable) returns).getMessage();
                    String n = returns.getClass().getSimpleName();
                    text = m == null ? n : n + ": " + m;
                } else if (returns instanceof ResponseMessage) {
                    ResponseMessage resp = (ResponseMessage) returns;
                    List<String> buffer = new ArrayList<>();
                    JSONObject json = (JSONObject) JSON.toJSON(resp);
                    for (Map.Entry<String, Object> entry : json.entrySet()) {
                        if (entry.getKey() == null || entry.getValue() == null) {
                            continue;
                        }
                        String string = JsonTools.toJsonString(entry.getValue());
                        buffer.add("\"" + entry.getKey() + "\":" + StringTools.ellipsis(string, 200));
                    }
                    text = "{" + ConvertTools.joinToString(buffer, ",") + "}";
                } else {
                    text = JSON.toJSONString(returns);
                }
            }

            // 响应信息
            LogResultType resultType = findResultType(returns);
            operateRecord.setReturnType(resultType);
            operateRecord.setReturnCode(code);
            operateRecord.setReturnContent(text);

            if (isNeedCopyUploadFile(file, resultType)) {
                // 保存上传的文件
                String url = saveUploadFile(file);
                if (VerifyTools.isNotBlank(url)) {
                    String name = file.getOriginalFilename();
                    operateRecord.getOptions(true).setAttachment(new RemoteAttachment(name, url));
                }
            }
            if (level.ordinal() >= saveToFile.ordinal()) {

                ParamBuffer buffer = ParamBuffer.newTabBuffer(objectStringMaxLength);
                buffer.append(curr);
                buffer.append(operateRecord.getProject());
                buffer.append(operateRecord.getRequestType());
                // 用户信息
                buffer.append(operateRecord.getUserId());
                buffer.append(operateRecord.getUserName());
                // 请求信息
                buffer.append(operateRecord.getIp());
                buffer.append(operateRecord.getRequestProtocol());
                buffer.append(operateRecord.getRequestMethod());
                buffer.append(operateRecord.getRequestUrl());
                buffer.append(operateRecord.getRequestParams());
                buffer.append(operateRecord.getRefererUrl());
                buffer.append(operateRecord.getUserAgent());
                // 执行信息
                buffer.append(operateRecord.getExecuteDesc());
                buffer.append(operateRecord.getExecuteMethod());
                buffer.append(operateRecord.getExecuteTime());
                buffer.append(operateRecord.getDbInvokeTimes());
                // 响应信息
                buffer.append(operateRecord.getReturnCode());
                buffer.append(operateRecord.getReturnContent());
                // 其他参数
                OperateRecordOptions options = operateRecord.getOptions();
                buffer.append(options == null ? null : JsonTools.toJsonString(options));

                for (OperateTracesBean item : operateItems) {
                    long elapsed = item.getCreateTime().getTime() - startTime.getTime();
                    buffer.newline(true);
                    buffer.append(ConvertTools.toDuration(elapsed, true));
                    buffer.append(item.getOperateType());
                    buffer.append(item.getDataType());
                    buffer.append(item.getDataId());
                    buffer.append(item.getDataCode());
                    buffer.append(item.getDataValue());
                    buffer.append(item.getDataDesc());
                    buffer.append(item.getExecuteDesc());
                    buffer.append(item.getExecuteMethod());
                    buffer.append(item.getExecuteTime());
                    buffer.append(item.getAffectedRows());
                }

                saveToFile(log, level, buffer.toString());
            }

            if (level.ordinal() >= saveToDb.ordinal()) {
                if (operateRecordExecutor == null || operateTracesExecutor == null) {
                    // service注入失败
                    long mills = System.currentTimeMillis();
                    if (mills - lastTimeOfServiceIsNull > DateTools.RATE_HOUR) {
                        lastTimeOfServiceIsNull = mills;
                        log.warn("Can't save access log to db, Service is null.");
                    }
                } else {
                    // 构造入库参数
                    saveToDb(operateRecord);

                    List<OperateTracesBean> buffer = new ArrayList<>();
                    for (OperateTracesBean item : operateItems) {
                        item.setRecordId(operateRecord.getId());
                        item.setTenantCode(operateRecord.getTenantCode());

                        buffer.add(item);
                        if (buffer.size() > 20) {
                            saveToDb(buffer);
                            buffer.clear();
                        }
                    }
                    saveToDb(buffer);
                    buffer.clear();
                }
            }
        }

        public void setLevel(LogLevel level) {
            this.level = level;
        }

        public void setPackages(String packages) {
            this.packages = packages;
        }

        public void setResponseBody(boolean isResponseBody) {
            this.isResponseBody = isResponseBody;
        }

        public void setAnnotation(OperateRecord annotation) {
            this.annotation = annotation;
        }

        public void setPermission(String permission) {
            this.permission = permission;
        }

        public void setReturns(Object returns) {
            this.returns = returns;
        }

        public void setOperateRecord(OperateRecordBean bean) {
            this.operateRecord = bean;
        }

        public void setOperateTracess(List<OperateTracesBean> operateItems) {
            this.operateItems = operateItems;
        }

        public void setUploadFile(MultipartFile file) {
            this.file = file;
        }
    }
}
